package com.irssproject.app.service.lectorRSS.impl;

import com.irssproject.app.entitats.Topic;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.htmlparser.jericho.Source;

import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.irssproject.app.dao.lectorRSS.iface.LabelsDAO;
import com.irssproject.app.dao.lectorRSS.iface.FeedsDAO;
import com.irssproject.app.dao.lectorRSS.iface.SubscriptionsDAO;
import com.irssproject.app.entitats.Label;
import com.irssproject.app.entitats.Feed;
import com.irssproject.app.entitats.Item;
import com.irssproject.app.entitats.Reading;
import com.irssproject.app.entitats.Subscription;
import com.irssproject.app.entitats.User;
import com.irssproject.app.service.lectorRSS.iface.SubscriptionsService;
import com.irssproject.app.utils.Constants;
import com.irssproject.app.web.action.JSONWrappers.FeedsByTopic;
import com.irssproject.app.web.action.JSONWrappers.ItemsByTopic;
import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedInput;
import com.sun.syndication.io.XmlReader;

@Transactional(propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT)
public class SubscriptionsServiceImpl implements SubscriptionsService {

    Logger logger = Logger.getLogger(SubscriptionsServiceImpl.class.getName());
    SubscriptionsDAO subscripcioDAO;
    FeedsDAO feedDAO;
    LabelsDAO etiquetaDAO;

    public void setLabelsDAO(LabelsDAO etiquetaDAO) {
        this.etiquetaDAO = etiquetaDAO;
    }

    public void setFeedsDAO(FeedsDAO feedDAO) {
        this.feedDAO = feedDAO;
    }

    public void setSubscriptionsDAO(SubscriptionsDAO subscripcioDAO) {
        this.subscripcioDAO = subscripcioDAO;
    }

    @Override
    public void insertSubscription(User user, String feedLink, String label, Boolean fromRecommendation) throws Exception {

        Subscription subscripcio = new Subscription();

        //Set user
        subscripcio.setUser(user);

        Feed feed = subscripcioDAO.getUserFeed(user, feedLink);

        if (feed == null) {
            //user doesnt have feed subscribed yet
            String feedTitle = readRSSFeed(feedLink);

            //look in the bd for feed
            feed = feedDAO.getFeed(feedLink);

            if (feed == null) {
                //if not exists create new
                Long idFeed = feedDAO.insertFeed(feedTitle, feedLink);
                feed = new Feed(idFeed, feedLink, feedTitle);
            }
        } else {
            throw new Exception(Constants.EXCEPTION_FEED_ALREADY_SUBSCRIBED);
        }
        //Set feed
        subscripcio.setFeed(feed);

        //check label not null and not empty
        if (label != null && !label.trim().isEmpty()) {
            Label lab = etiquetaDAO.getLabelByName(label);
            if (lab == null) {
                Long idLab = etiquetaDAO.insertLabel(new Label(label));
                lab = new Label(idLab, label);
            }
            //Set label
            subscripcio.setLabel(lab);
        }

        //read last items from feed
        List<Item> items = getLastFeedEntries(feed);

        //Set whether subscription comes from a recommendation
        subscripcio.setFromRecommendation(fromRecommendation);
        subscripcio.setUnreadItemsNumber(items.size());

        Reading read;
        Item existingItem;
        for (Item item : items) {
            read = new Reading();

            //check whether or not item exists
            existingItem = subscripcioDAO.getItemByLink(item.getLink());

            //if yes, set to the reading
            if (existingItem != null) {
                read.setItem(existingItem);
            } else {
                //else, get id and put it to item
                Long id = subscripcioDAO.insertItem(item);
                item.setId(id);
                read.setItem(item);
            }

            read.setSubscription(subscripcio);
            read.setRead(false);
            read.setReadingTime(0);
            read.setRelevance(0);
            read.setLiked(false);
            subscripcio.addReading(read);
        }
        //Set date last act. Jan 1 1970
        subscripcio.setLastUpdateDate(new Date(86400000));

        //At last insert subscription
        subscripcioDAO.insertSubscription(subscripcio);
    }

    /**
     * gets last items from feed (gets items without ids)
     * @param feed
     * @return
     * @throws Exception
     */
    private List<Item> getLastFeedEntries(Feed feed) throws Exception {
        List<Item> items = new ArrayList<Item>();
        try {
            URL feedUrl = new URL(feed.getLink());

            final SyndFeedInput input = new SyndFeedInput();

            final SyndFeed feedRSS = input.build(new XmlReader(feedUrl));

            Iterator entry = feedRSS.getEntries().iterator();
            Item item;

            while (entry.hasNext()) {
                item = new Item();

                SyndEntry en = (SyndEntry) entry.next();
                item.setTitle(en.getTitle());
                item.setLink(en.getLink());
                //Si la entry te data de publicacio, l'afegim
                if (en.getPublishedDate() != null) {
                    item.setPublishedDate(en.getPublishedDate());
                } // Sino, afegim la data actual
                else {
                    item.setPublishedDate(Calendar.getInstance().getTime());
                }
                item.setFeed(feed);
                if (en.getContents().size() > 0) {
                    item.setContent(((SyndContent) en.getContents().get(0)).getValue());
                } else if (en.getDescription() != null) {
                    item.setContent(en.getDescription().getValue());
                }
                items.add(item);
            }
        } catch (IllegalArgumentException e) {
            throw new Exception(Constants.EXCEPTION_ILLEGAL_FEED, e);
        } catch (FeedException e) {
            throw new Exception(Constants.EXCEPTION_GENERIC_FEED, e);
        } catch (MalformedURLException e) {
            throw new Exception(Constants.EXCEPTION_MALFORMED_FEED_URL, e);
        }
        return items;
    }

    @Override
    public void refreshSubscription(Long idSubscripcio) {
        Item item = new Item();
        List<Item> listNousItems = new ArrayList<Item>();

        Subscription subscripcio = subscripcioDAO.getSubscripcioById(idSubscripcio);
        Timestamp timestampDarrerItemPublicat = subscripcioDAO.getDarreraDataPublicacio(subscripcio);
        Date dataDarrerItemPublicat;
        if (timestampDarrerItemPublicat != null) {
            dataDarrerItemPublicat = new Date(timestampDarrerItemPublicat.getTime() + timestampDarrerItemPublicat.getNanos() / 1000000);
        } else {
            dataDarrerItemPublicat = new Date(0L);
        }

        Date dataActualItem;
        try {
            URL feedUrl = new URL(subscripcio.getFeed().getLink());
            final SyndFeedInput input = new SyndFeedInput();
            final SyndFeed feedRSS = input.build(new XmlReader(feedUrl));

            Iterator entries = feedRSS.getEntries().iterator();

            while (entries.hasNext()) {
                item = new Item();

                SyndEntry en = (SyndEntry) entries.next();

                //Si la data de publicacio de la entrada es mes nova que la data de l'ultima lectura
                //l'afegim a la llista de no llegits
                dataActualItem = en.getPublishedDate();

                //La data es opcional, per la qual cosa, si no te data de publicacio mirem si el item existeix per link,
                if (dataActualItem == null) {

                    Item aux = subscripcioDAO.getItemByLink(en.getLink());

                    if (aux == null) {

                        item.setTitle(en.getTitle());
                        item.setLink(en.getLink());
                        item.setPublishedDate(new Date());
                        item.setFeed(subscripcio.getFeed());

                        if (en.getContents().size() > 0) {
                            item.setContent(((SyndContent) en.getContents().get(0)).getValue());
                        } else if (en.getDescription() != null) {
                            item.setContent(en.getDescription().getValue());
                        }
                        logger.log(Level.INFO, "Adding new item from feed {0}  Item name: {1}. No published data (default today): {2}", new Object[]{subscripcio.getFeed().getTitol(), item.getTitle(), item.getPublishedDate()});
                        listNousItems.add(item);

                    }
                } else {
                    if (dataDarrerItemPublicat.getTime() / 1000 < dataActualItem.getTime() / 1000) {
                        item.setTitle(en.getTitle());
                        item.setLink(en.getLink());
                        item.setPublishedDate(en.getPublishedDate());
                        item.setFeed(subscripcio.getFeed());

                        if (en.getContents().size() > 0) {
                            item.setContent(((SyndContent) en.getContents().get(0)).getValue());
                        } else if (en.getDescription() != null) {
                            item.setContent(en.getDescription().getValue());
                        }
                        logger.log(Level.INFO, "Adding new item from feed {0}  Item name: {1}. Published data: {2}", new Object[]{subscripcio.getFeed().getTitol(), item.getTitle(), item.getPublishedDate()});
                        listNousItems.add(item);
                    }
                }
            }

            if (listNousItems.size() > 0) {

                Iterator it = listNousItems.iterator();
                Reading le;
                Item itemExisting;
                while (it.hasNext()) {
                    le = new Reading();
                    item = (Item) it.next();
                    itemExisting = subscripcioDAO.getItemByLink(item.getLink());
                    boolean existsReadingForUser = false;
                    if (itemExisting != null) {
                        
                        if (!subscripcioDAO.existsReading(subscripcio, itemExisting))
                            le.setItem(itemExisting);
                        else
                            existsReadingForUser = true;
                    } else {
                        item.setFeed(subscripcio.getFeed());
                        Long id = subscripcioDAO.insertItem(item);
                        item.setId(id);
                        le.setItem(item);
                    }
                    if (!existsReadingForUser) {
                        le.setSubscription(subscripcio);
                        le.setRead(false);
                        le.setReadingTime(0);
                        le.setRelevance(0);
                        le.setLiked(false);
                        le.setAddedDate(item.getPublishedDate());
                        subscripcio.addReading(le);
                    }
                }
                subscripcioDAO.refreshSubscripcio(subscripcio);
            }
        } catch (FeedException fe) {
            logger.log(Level.SEVERE, "Some problem with feed {0}: {1}", new Object[]{subscripcio.getFeed().getTitol(), fe.getLocalizedMessage()});
        } catch (MalformedURLException mue) {
            logger.log(Level.SEVERE, "Malformed URL {0}: {1}", new Object[]{subscripcio.getFeed().getTitol(), mue.getLocalizedMessage()});
        } catch (IOException fe) {
            logger.log(Level.SEVERE, "Problem with IO {0}: {1}", new Object[]{subscripcio.getFeed().getTitol(), fe.getLocalizedMessage()});
        } catch (Exception e) {
            e.printStackTrace();
            logger.log(Level.SEVERE, "Error adding feed", e.getMessage());
        }

    }

    public void deleteSubscription(Long idSubscripcio) {
        subscripcioDAO.deleteSubscripcio(idSubscripcio);
    }

    public List<Subscription> getSubscriptionsList(User usuari, Boolean fromRecommendation) {
        return subscripcioDAO.getListSubscripcions(usuari, fromRecommendation);
    }

    @Override
    public List<Label> getLabelsList(User usuari) {
        return subscripcioDAO.getListEtiquetes(usuari);
    }

    @Override
    public void markItemAsRead(Long idLectura) {
        Reading rd = subscripcioDAO.getLecturaById(idLectura);
        
        if (rd != null)
            subscripcioDAO.markItemAsReaded(rd, rd.getSubscription().getUnreadItemsNumber()-1);
    }

    @Override
    public List<Label> getLabelsList() {
        return etiquetaDAO.getListEtiquetes();
    }

    @Override
    public List<Feed> getFeedsList() {
        return feedDAO.getListFeeds();
    }

    @Override
    public void setReadingRelevance(Reading lectura) {
        Item itemComplet = lectura.getItem();

        Source source = new Source(itemComplet.getContent());
        String contentDirty = itemComplet.getContent();
        String content = source.getTextExtractor().setIncludeAttributes(true).toString();

        int contentLength = content.length();
        float tempsMinimActual = (((float) contentLength) * Constants.TEMPS_MINIM_LECTURA) / 1000;
        float tempsMaximActual = (((float) contentLength) * Constants.TEMPS_MAXIM_LECTURA) / 1000;

        if (lectura.isLiked() == true) {
            if (lectura.getReadingTime() > tempsMinimActual && lectura.getReadingTime() < tempsMaximActual) {
                lectura.setRelevance(3);
            } else {
                if (contentDirty.contains(Constants.EMBEDED_YOUTUBE_SEQ) || contentDirty.contains(Constants.SHOCKWAVE_FLASH_SEQ)) {
                    lectura.setRelevance(3);
                } else {
                    lectura.setRelevance(2);
                }
            }
        } else {
            if (lectura.getReadingTime() > tempsMinimActual && lectura.getReadingTime() < tempsMaximActual) {
                lectura.setRelevance(1);
            } else {
                if (contentDirty.contains(Constants.EMBEDED_YOUTUBE_SEQ) || contentDirty.contains(Constants.SHOCKWAVE_FLASH_SEQ)) {
                    lectura.setRelevance(1);
                } else {
                    lectura.setRelevance(0);
                }
            }


        }
        subscripcioDAO.definirRellevanciaDeLectura(lectura);
    }

    @Override
    public List<Reading> showReadingsOnInterval(Long idSubscripcio, Integer toElement) throws Exception {
        Subscription subscripcio = subscripcioDAO.getSubscripcioById(new Long(idSubscripcio));
        List<Reading> llistaLectures = subscripcioDAO.getReadingsOnInterval(subscripcio, toElement);
        Collections.sort(llistaLectures);
        return llistaLectures;
    }

    @Override
    	public List<Reading> showAllReadingsOnInterval(Long idSubscripcio, Integer toElement) throws Exception {
        Subscription subscripcio = subscripcioDAO.getSubscripcioById(new Long(idSubscripcio));
        //TODO modificar esta funcio tambe
        return subscripcioDAO.getAllReadingsOnInterval(subscripcio,10);

    }

    @Override
    public List<Reading> showLikedReadingsOnInterval(User usuari, Integer toElement) throws Exception {
        return subscripcioDAO.getStarredReadingsOnInterval(usuari, toElement);
    }

    /**
     * returns feed's title after reading it with XmlReader
     * @param feedLink
     * @return
     * @throws Exception
     */
    private String readRSSFeed(String feedLink) throws Exception {
        SyndFeed feedRSS = null;
        try {
            URL feedUrl = new URL(feedLink);
            final SyndFeedInput input = new SyndFeedInput();

            feedRSS = input.build(new XmlReader(feedUrl));

        } catch (IllegalArgumentException iae) {
            throw new Exception(Constants.EXCEPTION_ILLEGAL_FEED);
        } catch (FeedException fe) {
            throw new Exception(Constants.EXCEPTION_GENERIC_FEED);
        } catch (MalformedURLException mue) {
            throw new Exception(Constants.EXCEPTION_MALFORMED_FEED_URL);
        }

        return feedRSS.getTitle();
    }

    public Item getItem(Subscription subscripcio, Item item) {
        return subscripcioDAO.getItem(item);
    }

    public Integer getSubscriptionItemsNumber(Subscription subscripcio) {
        return subscripcioDAO.getNumItemsSubscripcio(subscripcio);
    }

    public Subscription getSubscriptionById(Long idSubscripcio) {
        return subscripcioDAO.getSubscripcioById(idSubscripcio);
    }

    public Reading getReadingById(Long idLectura) {
        return subscripcioDAO.getLecturaById(idLectura);
    }

    public boolean setReadingReadOrUnread(Long idLectura) {
        return subscripcioDAO.marcarLlegidaONoLlegida(idLectura);
    }

    public boolean setReadingLikedOrUnliked(Long idLectura) {
        return subscripcioDAO.valorarLectura(idLectura);
    }

    public void setAllReadingsRead(Long idSubscripcio) {
        subscripcioDAO.marcarTotesLecturesLlegides(idSubscripcio);
    }

    @Override
    public List<Feed> getFeedsLikeList(String likeNom) {
        return feedDAO.getLlistaFeeds(likeNom);
    }

    @Override
    public Item getItemById(Long id) {
        return subscripcioDAO.getItemById(id);
    }

    @Override
    public List<Label> getLabelLikeName(String terme) {
        return etiquetaDAO.getEtiquetaLikeNom(terme);
    }

    @Override
    public List<User> getUsersSubscribed(User connectedUser) {
        return subscripcioDAO.getUsersSubscribed(connectedUser);
    }

    @Override
    public List<Topic> getTopicsSubscribed(User connectedUser) {
        return subscripcioDAO.getTopicsSubscribed(connectedUser);
    }

    @Override
    public List<FeedsByTopic> getFeedsLstByTopic(User user, String topic) {
        
        List<FeedsByTopic> feedsByTopic = new ArrayList<FeedsByTopic>();
        List<Topic> lstTopic = subscripcioDAO.getTopicLstLikeName(topic);
        List<Feed> feedsLst = null;
        for (Topic t:lstTopic){
            feedsLst = new ArrayList<Feed>();
            feedsLst.addAll(subscripcioDAO.getFeedsLstByTopic(user, t));
            if (feedsLst.size()>0)
                feedsByTopic.add(new FeedsByTopic(t, feedsLst));
        }
        return feedsByTopic;
    }

    @Override
    public List<ItemsByTopic> getItemsLstByTopic(User connectedUser, String topic) {
        
        List<ItemsByTopic> itemsByTopic = new ArrayList<ItemsByTopic>();
        List<Topic> lstTopic = subscripcioDAO.getTopicLstLikeName(topic);
        List<Item> itemsLst;
        for (Topic t:lstTopic){
            itemsLst = new ArrayList<Item>();
            itemsLst.addAll(subscripcioDAO.getItemsLstByTopic(connectedUser, t));
            if (itemsLst.size()>0)
                itemsByTopic.add(new ItemsByTopic(t, itemsLst));
        }
        return itemsByTopic;
    }
}
